---
title: Data Schemas
---

GPU programs usually involve sharing data between the host (CPU) and the device (GPU).
In the case of WebGPU, even though data is strongly typed in WGSL shaders, the communication between JavaScript and WGSL involves reading raw bytes from buffers.
Any misalignments or misinterpretations of data can lead to faulty code that is difficult to debug.
This is precisely what TypeGPU data schemas, like `d.u32` or `d.vec3f`, help with.

:::note
Complex data types such as structs require particular byte alignment and padding between fields.
TypeGPU does this automatically during buffer writes and reads.
:::

The primary usage for data schemas is to define buffer and function signatures.
Schemas can also be called in JS or TGSL to create instances of the types they represent.

TypeGPU provides data schemas for scalar, vector and matrix types, as well as constructors for struct and array schemas.

## Scalars, vectors and matrices

### Constructors

For scalar WGSL types `f32`, `u32`, `i32`, `f16` and `bool`, TypeGPU provides `d.f32`, `d.u32`, `d.i32`, `d.f16` and `d.bool` schemas respectively.
Schemas can be called to cast a value to a given type.
This works both in TGSL, and in plain JavaScript.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
d.u32(31.1); // 31
d.u32(-1); // 4294967295
d.f32(true); // 1
d.i32(); // 0 (default value)
d.bool(0.01); // true
```

:::caution
On JavaScript side, the type of any number remains a number regardless of any casts.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const myUnsignedInt = d.u32(1);
//    ^?
const result = d.u32(3)/d.u32(2); // 1.5
```

For the WGSL and JS functions to return consistent results, during WGSL resolution TypeGPU wraps all divisions in `f32` casts.
:::

For each of the scalar types, there is a vector schema of 2, 3 and 4 elements of that type.
For vector WGSL types `vec2f`, `vec2u`, `vec2i`, `vec2h` and `vec2<bool>`, TypeGPU provides `d.vec2f`, `d.vec2u`, `d.vec2i`, `d.vec2h` and `d.vec2b` schemas respectively. Analogously for 3 and 4 components.

Just like scalar schemas, vector schemas can be called to create values of corresponding types.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const v1 = d.vec3f(1, 2, 3); // a vector of three elements [1, 2, 3]
//    ^?
const v2 = d.vec4f(v1.x, v1[1], 4, 5); // a vector of four elements [1, 2, 4, 5]
const v3 = v1.xy; // a vector of two elements [1, 2]
const v4 = d.vec4f(0, v3, 3); // a vector of four elements [0, 1, 2, 3]
```

:::note
Be mindful not to confuse the schema (e.g. `d.vec2f`), a schema instance (`d.vec2f()`), the schema type (`d.Vec2f`), and the instance type (`d.v2f`).

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const VectorSchema = d.vec2f;
//    ^?
const vectorInstance = d.vec2f(1, 2);
//    ^?
```

:::

Matrices work similarly.
For matrix WGSL types `mat2x2f`, `mat3x3f` and `mat4x4f`, TypeGPU provides `d.mat2x2f`, `d.mat3x3f` and `d.mat4x4f` schemas respectively.
Matrices of other sizes, as well as matrices of `f16`, are currently not supported by TypeGPU.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const mat1 = d.mat2x2f(); // empty matrix
//    ^?

const mat2 = d.mat3x3f(
//    ^?
  1.1, 2.5, 3.3,
  1.2, 2.6, 3.4,
  1.3, 2.7, 3.5,
);

const firstColumn = mat2.columns[0]; // d.vec3f(1.1, 2.5, 3.3)
//    ^?
```

:::tip
Performing vector and matrix math operations with TypeGPU values is possible due
to a seamless integration with `wgpu-matrix`, which you can [read more about here](/TypeGPU/integration/working-with-wgpu-matrix).
:::

:::caution
For `wgpu-matrix` integration, matrix instances implement array access.
Matrix array access is not supported in TGSL.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const mat = d.mat2x2f(1.1, 2.2, 3.3, 4.4);
const firstElement = mat[0]; // 1.1 in JS, but resolves to invalid WGSL!
//    ^?
```

:::

### Operators

As you may know, it is currently impossible to overload operators in JavaScript.
For that reason, TypeGPU provides `std` functions imitating those.

```ts twoslash
import * as d from 'typegpu/data';
import * as std from 'typegpu/std';

const vec1 = d.vec3f(1, 2, 3);
const vec2 = std.add(vec1, 3); // d.vec3f(4, 5, 6)
//    ^?

const mat = d.mat3x3f(
  2, 0, 0,
  0, 4, 0,
  0, 0, 8,
);

const multiplication = std.mul(vec2, mat); // d.vec3f(8, 20, 48);
//    ^?

const comparison = std.le(d.vec3f(2, 3, 4), d.vec3f(3)); // d.vec3b(true, true, false)
//    ^?
```

Currently, implemented operators include:

- arithmetic operators `add`, `sub`, `mul`, `div`, `mod`, `neg`,
- comparison operators `eq`, `ne`, `lt`, `le`, `gt`, `ge`,
- logical operators `not`, `and`, `or`.

When operating on scalars, you can still use `+`, `<=`, `!` etc.,
the purpose of these `std` functions is to enable working on vectors and matrices.

For arithmetic operators, you can also use the infix notation.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const v1 = d.vec2f(1, 2);
const v2 = d.vec2f(3, 4);

const addition = v1.add(v2); // same as std.add(v1, v2)
const multiplication = v1.mul(v2).mul(3); // same as std.mul(std.mul(v1, v2), 3)
```

:::note
Infix operators return completely new objects, they don't modify in-place.
:::

## Structs

Here is an example of defining a custom compound data type using the `d.struct` function.

```ts twoslash
import * as d from 'typegpu/data';

const Circle = d.struct({
  centerPos: d.vec3i,
  radius: d.f32,
});

const redCircle = Circle({
  centerPos: d.vec3i(2, 4, 0),
  radius: 0.2,
});
```

If you're familiar with [Zod](https://zod.dev/), then this style of schema definitions may already seem familiar.
We define the `Circle` struct, similarly to how we would in WGSL, and then we can use it to validate and cast our data values.
When reading from or writing data to the GPU, the type of the JavaScript value is inferred automatically.

```ts twoslash
// @errors: 2740 2322 2339 2345

import * as d from 'typegpu/data';

const Circle = d.struct({
  centerPos: d.vec3i,
  radius: d.f32,
});

// ---cut---
const redCircle1 = Circle({
  centerPos: d.vec2i(2, 4),
  radius: 0.2,
});

const redCircle2 = Circle({
  centerPos: d.vec3i(2, 4, 0),
  radius: "0.2",
});

const redCircle3 = Circle({
  centerPos: d.vec3i(2, 4, 0),
});

const diam = redCircle1.rad * 2;
```

Struct schemas also allow for default value creation and code autocompletion.

```ts twoslash
// @noErrors
import * as d from 'typegpu/data';

const Boid = d.struct({
  position: d.vec3u,
  velocity: d.vec3f,
  color: d.vec4f,
  isActive: d.bool,
});

// ---cut---
const defaultBoid = Boid(); // zero-filled boid

const boid = Boid({
  position: d.vec3u(0, 0, 0),
  velocity: d.vec3f(1, 0.5, 0.5),
  color: d.vec4f(1.0, 0.2, 0.3, 1.0),
  isAct
  //   ^|
});
```

Structs can also be nested.

```ts twoslash
import * as d from 'typegpu/data';
//---cut---
const FishState = d.struct({
  pos: d.vec3f,
  vel: d.vec3f,
});

const Fish = d.struct({
  kind: d.u32,
  state: FishState, // nested struct
});

const instance = Fish({ kind: 0, state: { pos: d.vec3f(), vel: d.vec3f() } });
//    ^?
```

:::caution
You cannot use nested structs in [vertex layouts](/TypeGPU/fundamentals/vertex-layouts).
:::

If you wish to extract the JS type equivalent of a TypeGPU schema, you can do so with `d.Infer`:

```ts twoslash
import * as d from 'typegpu/data';
import { sizeOf, alignmentOf } from 'typegpu/data';

const Circle = d.struct({
  centerPos: d.vec3i,
  radius: d.f32,
});
// ---cut---
type u32 = d.Infer<typeof d.u32>;
//   ^?
type Circle = d.Infer<typeof Circle>;
//   ^?
```

Defined data structures automatically measure and hold information about their memory layout parameters.

```ts twoslash
import * as d from 'typegpu/data';

const Circle = d.struct({
  centerPos: d.vec3i,
  radius: d.f32,
});

type Circle = d.Infer<typeof Circle>;

// ---cut---
d.sizeOf(Circle) // 16
d.alignmentOf(Circle) // 16
```

Struct schemas adjust the padding and alignment automatically, so that they comply with WebGPU's memory alignment rules.
It is also possible to override default byte alignment and size for particular fields via the `d.align` and `d.size` functions.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const Boid = d.struct({
  position: d.align(32, d.vec3u), // Aligned to multiples of 32 bytes
  velocity: d.vec3f,
  color: d.vec4f,
  isActive: d.size(8, d.bool), // Has a minimum size of 8 bytes
});
```

## Arrays

To define arrays of known constant length, use the `d.arrayOf` function with the schema representing the element type and with the length of the array.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const MyArray = d.arrayOf(d.f32, 4);

const myDefaultArray = MyArray(); // [0, 0, 0, 0]
//    ^?

const myInitializedArray = MyArray([1, 0, 0.5, 20]);
```

You can also call `d.arrayOf` without specifying the array length. This returns a partially applied schema function, allowing you to provide the length later or use the schema to declare WGSL [runtime-sized arrays](/TypeGPU/fundamentals/bind-groups#runtime-sized-example).

```ts twoslash
import * as d from 'typegpu/data';

// ---cut---
const ArrayPartialSchema = d.arrayOf(d.f32);
//    ^?

const array = ArrayPartialSchema(2)([1.2, 19.29]);
//    ^?
```

## Textures

Texture schemas serve two main purposes:
 - defining texture views (both fixed and in layouts)
 - providing argument types for user defined functions

```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
import * as std from 'typegpu/std';

const root = await tgpu.init();
const texture = root['~unstable'].createTexture({
  size: [256, 256],
  format: 'rgba8unorm',
}).$usage('sampled', 'storage');

// ---cut---
const storeOnes = (tex: d.textureStorage2d<'rgba8unorm'>, coords: d.v2u) => {
  'use gpu';
  std.textureStore(tex, coords, d.vec4f(1));
};

const storeOnesShelled = tgpu.fn([d.textureStorage2d('rgba8unorm'), d.vec2u])(
  (tex, coords) => {
    std.textureStore(tex, coords, d.vec4f(1));
  },
);

const sampledView = texture.createView(d.texture2d(d.f32));
//     ^?

const storageView = texture.createView(d.textureStorage2d('rgba8unorm', 'read-only'));
//     ^?

const layout = tgpu.bindGroupLayout({
  //     ^?
  sampled: { texture: d.texture2d() },
  storage: { storageTexture: d.textureStorage2d('rgba8unorm', 'read-only') },
});
```

### Sampled Textures

Sampled texture schemas are created using one of the following constructors:

- **`d.texture1d(sampleType?)`** - A 1D texture
- **`d.texture2d(sampleType?)`** - A 2D texture
- **`d.texture2dArray(sampleType?)`** - A 2D array texture
- **`d.texture3d(sampleType?)`** - A 3D texture
- **`d.textureCube(sampleType?)`** - A cube texture
- **`d.textureCubeArray(sampleType?)`** - A cube array texture
- **`d.textureMultisampled2d(sampleType?)`** - A 2D multisampled texture

The `sampleType` parameter can be `d.f32`, `d.i32`, or `d.u32`, determining how the texture data will be interpreted. If omitted, it defaults to `d.f32`.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const tex1 = d.texture2d(d.f32); // float texture (default)
//    ^?

const tex2 = d.texture2d(d.u32); // unsigned integer texture
//    ^?

const tex3 = d.texture2dArray(); // defaults to f32
//    ^?
```

#### Depth Textures

For depth comparison operations, TypeGPU provides specialized depth texture schemas:

- **`d.textureDepth2d()`** - A 2D depth texture
- **`d.textureDepthMultisampled2d()`** - A 2D multisampled depth texture
- **`d.textureDepth2dArray()`** - A 2D array depth texture
- **`d.textureDepthCube()`** - A cube depth texture
- **`d.textureDepthCubeArray()`** - A cube array depth texture

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const depthTex = d.textureDepth2d();
//    ^?
```

### Storage Textures

Storage texture schemas are created using dimension-specific constructors, with required `format` and optional `access` parameters:

- **`d.textureStorage1d(format, access?)`** - A 1D storage texture
- **`d.textureStorage2d(format, access?)`** - A 2D storage texture
- **`d.textureStorage2dArray(format, access?)`** - A 2D array storage texture
- **`d.textureStorage3d(format, access?)`** - A 3D storage texture

The `format` parameter specifies the texture format (e.g., `'rgba8unorm'`, `'rgba16float'`, `'r32float'`), and the `access` parameter can be `'write-only'`, `'read-only'`, or `'read-write'`. If `access` is omitted, it defaults to `'write-only'`.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const storageTex1 = d.textureStorage2d('rgba8unorm');
//    ^?

const storageTex2 = d.textureStorage2d('rgba8unorm', 'read-only');
//    ^?

const storageTex3 = d.textureStorage3d('r32float', 'read-write');
//    ^?
```

## Atomics

To create a schema corresponding to an atomic data type, wrap `d.i32` or `d.u32` with `d.atomic`.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const AtomicI32 = d.atomic(d.i32);
//    ^?
```

:::tip
The `std` module provides functions for manipulating atomic data in TGSL.

```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
import * as std from 'typegpu/std';
// ---cut---
const count = tgpu.workgroupVar(d.atomic(d.u32));

const incrementAndGet = tgpu.fn([], d.u32)(() => {
  return std.atomicAdd(count.$, 1);
});
```

:::

## Pointers

To create a schema corresponding to a pointer data type, wrap the inner schema with a pointer constructor corresponding to the appropriate address space.

For address spaces `function`, `private`, `workgroup`, `storage` and `uniform`, use `d.ptrFn`, `d.ptrPrivate`, `d.ptrWorkgroup`, `d.ptrStorage` and `d.ptrUniform` respectively.

```ts twoslash
import * as d from 'typegpu/data';
// ---cut---
const vecPtrFn = d.ptrFn(d.vec3f);
//    ^?

const vecPtrPrivate = d.ptrPrivate(d.vec2f);
//    ^?
```

:::note
In order to use `workgroup`, `storage` and `uniform` pointers as function parameters,
WGSL needs the `unrestricted_pointer_parameters` extension.

This extension is enabled automatically when available.
:::

## Copy constructors

One of the biggest differences between JavaScript and WGSL is the existence of value/reference types.
Let's take a look at a simple TGSL function and the WGSL code it resolves to.

```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
// ---cut---
// TGSL
const MyStruct = d.struct({ n: d.u32 });

const myFn = tgpu.fn([])(() => {
  const s1 = MyStruct({ n: 0 });
  const s2 = s1;
  s2.n = 1; // s1 is modified
});
```

```wgsl
// WGSL
struct MyStruct {
  n: u32,
}

fn myFn(){
  var s1 = MyStruct(0);
  var s2 = s1;
  s2.n = 1; // s1 is not modified
}
```

On the JS side, s2 is a reference to s1 and therefore s1 is modified when s2 is modified.
On the WGSL side, the assignment operator copies the value of s1, and thus s1 is not modified later on.

To help with this issue, schema calls can be used as "copy constructors", that copy the value on the JS side, and disappear on the WGSL side:

```ts twoslash
import tgpu from 'typegpu';
import * as d from 'typegpu/data';
// ---cut---
// TGSL
const MyStruct = d.struct({ n: d.u32 });

const myFn = tgpu.fn([])(() => {
  const s1 = MyStruct({ n: 0 });
  const s2 = MyStruct(s1); // deep copy of s1
  s2.n = 1; // s1 is not modified
});
```

```wgsl
// WGSL
struct MyStruct {
  n: u32,
}

fn myFn(){
  var s1 = MyStruct(0);
  var s2 = s1; // copies s1 into s2
  s2.n = 1; // s1 is not modified
}
```
